﻿using System;
using System.Collections.Generic;
using Common.Logging;

namespace Common.Logging.Loupe
{
    /// <summary>
    /// Provides a factory to create proxies between the Common.Logging facade and Loupe
    /// </summary>
    public class LoupeFactoryAdapter: ILoggerFactoryAdapter
    {
        #region Fields

        /// <summary>
        /// Key that determines what level should be logged - used for
        /// initialization
        /// </summary>
        private const string LEVEL_PARAM = "level";
        
        // Collection of loggers that have already been created
        private readonly Dictionary<string, LoupeLogger> _cachedLoggers;

        /// <summary>
        /// The currently active log level.
        /// </summary>
        private LogLevel _currentLevel;

        #endregion

        #region Constructor

        /// <summary>
        /// Creates a new instance of the LoupeFactoryAdapter
        /// </summary>
        /// <param name="properties">Properties for intialization.</param>
        public LoupeFactoryAdapter(System.Collections.Specialized.NameValueCollection properties): this()
        {
            InitializeWithLevel(properties[LEVEL_PARAM]);
        }

        
        // This method is only valid for Common.Logging 2.2 or above
        /// <summary>
        /// Creates a new instance of the LoupeFactoryAdapter
        /// </summary>
        /// <param name="properties">Properties for intialization.</param>
        /// <remarks>This method is only available for Common.Logging 2.2 and above.</remarks>
        public LoupeFactoryAdapter(Common.Logging.Configuration.NameValueCollection properties): this()
        {
            InitializeWithLevel(properties[LEVEL_PARAM]);
        }

        /// <summary>
        /// Creates a new instance of LoupeFactoryAdapter()
        /// </summary>
        public LoupeFactoryAdapter()
        {
            _cachedLoggers = new Dictionary<string, LoupeLogger>();
        }

        #endregion 

        #region Properties

        /// <summary>
        /// Gets or sets the currently active logging level.
        /// </summary>
        public LogLevel CurrentLevel
        {
            get { return _currentLevel; }
            set
            {
                if (_currentLevel != value)
                {
                    _currentLevel = value;

                    // Changed value - update all cached loggers as well
                    lock (_cachedLoggers)
                    {
                        foreach (var logger in _cachedLoggers.Values)
                        {
                            logger.CurrentLevel = _currentLevel;
                        }
                    }
                }
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Get a logger for the current provided name
        /// </summary>
        /// <param name="name">Name of the class for the logger</param>
        /// <returns>An ILog implementation</returns>
        public ILog GetLogger(string name)
        {
            LoupeLogger logger = null;

            // Can't get a logger if the provided key is null or empty
            if (string.IsNullOrEmpty(name)) throw new ArgumentException("Name cannot be null or empty.");
                        
            lock (_cachedLoggers)
            {
                // We're just locking the existing Dictionary so we can compile against
                // 3.5 and below - alternatively we could use the ConcurrentDictionary
                // if we don't care about older target frameworks.
               
                if (!_cachedLoggers.TryGetValue(name, out logger))
                {
                    // Create a new logger and add it to the cache.
                    logger = new LoupeLogger(name, CurrentLevel);
                    _cachedLoggers.Add(name, logger);
                }
            }

            return logger;
        }

        /// <summary>
        /// Get a logger based on the provided type.
        /// </summary>
        /// <param name="type">The type for the logger.</param>
        /// <returns>An ILog implementation.</returns>
        public ILog GetLogger(Type type)
        {
            if (type == null) throw new ArgumentNullException("Type cannot be null.");
            return GetLogger(type.FullName);
        }

        private void InitializeWithLevel(string level)
        {
            LogLevel levelToSet;
            if (!string.IsNullOrEmpty(level) && ParsableEnum<LogLevel>.TryParse(level, out levelToSet))
            {
                // Do nothing, we parsed it
            }
            else
            {
                // Fall back to the default level
                levelToSet = LogLevel.Off;
            }

            this.CurrentLevel = levelToSet;
        }

        #endregion        
    }
}
